package com.cy.pj.sys.service.aspect;

import com.cy.pj.sys.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;


/**
 *  在Spring AOP 应用中,基于@Aspect注解描述的
 *  类型为一个切面类型,此类中要封装切入点及通知方法的定义
 *  1)切入点:要切入扩展业务逻辑的一些目标方法集合(例如使用了特点注解描述的方法)
 *  2)通知: 封装了扩展业务逻辑的一个方法(spring中也可使用特定注解进行描述)
 */
@Aspect
@Component
public class SysLogAspect {

    private static final Logger log =
            LoggerFactory.getLogger(SysLogAspect.class);

    @Pointcut("@annotation(com.cy.pj.sys.common.annotation.RequiredLog)")
    public void doLog(){
        //此方法用于承载切入点的定义
    }

    /**
     * 定义一个通知方法,这里使用@Around进行描述,标识在此方法内部可以调用
     * 目标方法,可以在目标方法执行之前或之后做一些拓展业务
     * @param pj 连接点, 用于封装要执行的目标切入点方法信息,
     *           ProceedingJoinPoint此类型参数只能定义在,@Around注解描述的方法中
     * @return   这里的返回值一般为目标切入点方法的执行结果
     *  如果有启动分页查询,可以不用返回结果集,因为分页mybatis中startPage拦截器在数据持久层就拿到了数据
     *  而aop拦截器是作用在Service层.所以这里有没有返回值就无所谓了,但是如果没有使用
     *  分页查询,就一定要放回值,不让结果集为null
     * @throws Throwable
     */
    @Around("@annotation(com.cy.pj.sys.common.annotation.RequiredLog)")
    public Object doAround(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("开始执行SysLogAspect..........");
        long t1 = System.currentTimeMillis();
        try {
            Object result = pj.proceed();;
            Long t2 = System.currentTimeMillis();
            log.info("opertime:{}",t2-t1);

            // pj.getTarget().getClass().getDeclaredMethod("这个传方法名");

            System.out.println("pj=   "+pj);
            System.out.println("SysLogAspect执行结束了");
            //主线程启动完线程后就会执行下一个语句(return result)
            doSaveLogInfo(pj,(t2-t1),null);//springboot池中的线程

            return result;
        }catch (Throwable e){
            e.printStackTrace();
            long t2 = System.currentTimeMillis();
            log.info("exception:{}",e.getMessage());
            doSaveLogInfo(pj,(t2-t1),e);
            throw e;
        }

    }

    @Autowired
    private SysLogService sysLogService;
    private void doSaveLogInfo(ProceedingJoinPoint jp, long time, Throwable e) throws NoSuchMethodException, JsonProcessingException {
        //获取用户行为日志
        //1.1获取登录用户信息
        String username = "tony";//后续是系统的登录用户
        //1.2获取登录用户的ip地址
        String ip = "192.168.100.11";//后续可以借助三方工具类
        //1.3获取用户操作
        //获取方法所在类的字节码对象(目标对象对应的字节码对象)
        Class clz =  jp.getTarget().getClass();
        //获取注解描述的方法对象(字节码对象,方法名,参数列表)
        Signature signature = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod =
        clz.getDeclaredMethod(methodSignature.getName(),methodSignature.getParameterTypes());
        //1.3.3获取RequiredLog注解
        RequiredLog requiredLog =  targetMethod.getAnnotation(RequiredLog.class);
        String operation = requiredLog.operation();
        String method = jp.getClass().getName();
        //1.5获取方法执行时传入的实际参数
        Object[] args = jp.getArgs();//实际参数
        String params = new ObjectMapper().writeValueAsString(args);
        //1.6获取状态信息
        Integer status = e==null?1:0;
        //1.7获取错误信息
        String error = e==null?null:e.getMessage();

        //2.封装用户行为日志
        SysLog sysLog = new SysLog();
        sysLog.setUsername(username);
        sysLog.setId(156L);
        sysLog.setCreatedTime(new Date());
        sysLog.setOperation(operation);
        sysLog.setMethod(method);
        sysLog.setParams(params);
        sysLog.setStatus(status);
        sysLog.setError(error);
        sysLog.setTime(time);
        //将日志写入到数据库
        log.info("user log {}",new ObjectMapper().writeValueAsString(sysLog));
        sysLogService.insertLog(sysLog);
    }
}
